package org.tulkas.config;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Enumeration;
import java.util.Properties;

import org.tulkas.util.FileUtil;

/**
 * Singleton Configuration class for the entire tulkas application. It loads all the configuration data at startup time, stores them and provide access to them. </BR>
 * All the configuration are in the form of Java properties file.<BR/>
 * <BR/>
 * This class tries to read config in 2 ways:<BR/>
 * 1. Search the classpath for org/tulkas/robot/config/default.properties resources. load the config from all these files.<BR/>
 * 2. Search in the data path for conf/configuration.properties file. Load the config from it.<BR/>
 * The class merges all the configurations from the 2 kinds of places. The #2 overrides #1.<BR/>
 * When the configurations are modified and saved. Only the changed config will be saved to #2 place.<BR/>
 * <BR/>
 * This class determines the root data path in the following sequence:<BR/>
 * 1. Read the system property: tulkas.robot.home<BR/>
 * 2. If #1 is null read the system property: user.dir <BR/>
 * 
 * @author <a href="mailto:tulkas.web@gmail.com">Tulkas</a>
 *
 */
public final class GlobalConfig extends Configuration {
	private static final String DEFAULT_DIR_NAME = ".tulkas";

	public static String CUSTOM_CONFIG_FILE = "conf/configuration.properties";
	public static String DEFAULT_CONFIG_PATH = "META-INF/tulkas-default.properties";

	private volatile static GlobalConfig instance = null;

	private BasicConfig defaultConf = new BasicConfig();
	private BasicConfig customConf = new BasicConfig(defaultConf);

	private String homePath = null;

	public static GlobalConfig getInstance() {
		if (instance == null) {
			synchronized (GlobalConfig.class) {
				if (instance == null)
					instance = new GlobalConfig();
			}
		}
		return instance;
	}

	public static void init(String homePath) {
		synchronized (GlobalConfig.class) {
			instance = new GlobalConfig(homePath);
		}
	}

	public static void destroyInstance() {
		synchronized (GlobalConfig.class) {
			instance = null;
		}
	}

	private GlobalConfig() {
		String loc = System.getProperty("user.home");
		initConfig(FileUtil.trimFilePath(loc) + "/" + DEFAULT_DIR_NAME);
	}

	private void initConfig(String homePath) {
		this.homePath = homePath;
		try {
			this.load();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	private GlobalConfig(String dataPath) {
		initConfig(dataPath);
	}

	public String getHomePath() {
		return homePath;
	}

	/**
	 * Load the config from all the config files in the classpath and the root data path.
	 * 
	 * @throws IOException
	 */
	public void load() throws IOException {
		defaultConf.clear();
		customConf.clear();
		Enumeration<URL> urls = GlobalConfig.class.getClassLoader()
				.getResources(DEFAULT_CONFIG_PATH);
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			InputStream is = url.openStream();
			defaultConf.load(new InputStreamReader(is));
			is.close();
		}

		String path = homePath + "/" + CUSTOM_CONFIG_FILE;
		if (new File(path).exists()) {
			FileInputStream fis = new FileInputStream(path);
			customConf.load(fis);
			fis.close();
		}
	}

	/**
	 * Store the modified config to conf/configuration.properties in root data path;
	 * 
	 * @throws IOException
	 */
	public void save() throws IOException {
		String path = homePath + "/" + CUSTOM_CONFIG_FILE;
		new File(FileUtil.getFileDir(path)).mkdirs();
		FileOutputStream fos = new FileOutputStream(path);
		customConf.save(fos);
		fos.close();
	}

	@Override
	public void clear() {
		customConf.clear();
	}

	@Override
	public boolean containsKey(String key) {
		return customConf.containsKey(key);
	}

	@Override
	public String[] getAllKeys(String prefix) {
		return customConf.getAllKeys(prefix);
	}

	@Override
	public String getValue(String key) {
		return customConf.getValue(key);
	}

	@Override
	public void setValue(String key, String value) {
		customConf.setValue(key, value);
	}

	@Override
	public void remove(String key) {
		customConf.remove(key);
	}

	public BasicConfig getSubConfig(String prefix) {
		return customConf.getSubConfig(prefix);
	}

	public Properties getSubProperties(String prefix) {
		return customConf.getSubProperties(prefix);
	}

}
